using System.Collections;
using System.Collections.Generic;
using Nwc.XmlRpc;

namespace EnsoDeveloperExtension
{
    /// <summary>
    /// Singelton class to handle Enso Developer Extensions.
    /// </summary>
    public class EnsoExtensionManager
    {
        #region Singelton

        private static EnsoExtensionManager _instance;
        private static object _lock = new object();

        public static EnsoExtensionManager Instance
        {
            get
            {
                lock (_lock)
                {
                    if (_instance == null)
                        _instance = new EnsoExtensionManager();
                }
                return _instance;
            }
        }

        private EnsoExtensionManager()
        {
            _port = ExtensionPort;
            _ensoCommands = new Dictionary<string, IEnsoCommand>();
        }

        #endregion

        private const string EnsoAddress = "http://127.0.0.1";
        private const int EnsoPort = 11374;
        private const string ExtensionAddress = "127.0.0.1";
        private const int ExtensionPort = 11378;

        private XmlRpcServer _server;
        private int _port;
        private readonly IDictionary<string, IEnsoCommand> _ensoCommands;

        /// <summary>
        /// Starts the extension server with the default port.
        /// </summary>
        public void Start()
        {
            _server = new XmlRpcServer(ExtensionAddress, _port);
            _server.Add("callCommand", this);
            _server.Start();
        }

        /// <summary>
        /// Starts the extension server with the specified port.
        /// </summary>
        /// <param name="extensionPort"></param>
        public void Start(int extensionPort)
        {
            _port = extensionPort;
            Start();
        }


        /// <summary>
        /// Stops the server and unregister all commands.
        /// </summary>
        public void Stop()
        {
            RemoveCommands();
            _server.Stop();
        }

        
        /// <summary>
        /// This is the function that must be implemented so Enso can talk to us.
        /// </summary>
        /// <param name="name">The command that was executed from Enso</param>
        /// <param name="postfix"></param>
        public void callCommand(string name, string postfix)
        {
            if (!_ensoCommands.ContainsKey(name))
                return;

            IEnsoCommand command = _ensoCommands[name];
            command.Call(postfix);
        }

        /// <summary>
        /// Add a command to the extension manager and register it with Enso.
        /// </summary>
        /// <param name="command"></param>
        public void AddCommand(IEnsoCommand command)
        {
            if (_ensoCommands.ContainsKey(command.Name))
                return;

            _ensoCommands.Add(command.Name, command);
            RegisterCommand(command.Name, command.Description, command.Help, command.PostfixType);
        }


        /// <summary>
        /// Remove a command from the ExtensionManager and unregister it from Enso.
        /// </summary>
        /// <param name="command"></param>
        public void RemoveCommand(IEnsoCommand command)
        {
            if (!_ensoCommands.ContainsKey(command.Name))
                return;

            UnregisterCommand(command.Name);
            _ensoCommands.Remove(command.Name);
        }

        /// <summary>
        /// Remove all commands from the ExtensionManager and unregister them from Enso.
        /// </summary>
        public void RemoveCommands()
        {
            foreach (IEnsoCommand command in _ensoCommands.Values)
            {
                UnregisterCommand(command.Name);
            }
            _ensoCommands.Clear();
        }

        /// <summary>
        /// This is registerCommand in the Developer API.
        /// </summary>
        /// <param name="name"></param>
        /// <param name="description"></param>
        /// <param name="help"></param>
        /// <param name="postfixType"></param>
        public void RegisterCommand(string name, string description, string help, string postfixType)
        {
            XmlRpcRequest client = new XmlRpcRequest();
            client.MethodName = "registerCommand";
            client.Params.Add(ExtensionUrl);
            client.Params.Add(name);
            client.Params.Add(description);
            client.Params.Add(help);
            client.Params.Add(postfixType);
            client.Send(EnsoUrl);
        }

        /// <summary>
        /// This is unregisterCommand in the Developer API.
        /// </summary>
        /// <param name="name"></param>
        public void UnregisterCommand(string name)
        {
            XmlRpcRequest client = new XmlRpcRequest();
            client.MethodName = "unregisterCommand";
            client.Params.Add(ExtensionUrl);
            client.Params.Add(name);
            client.Send(EnsoUrl);
        }

        /// <summary>
        /// This is displayMessage in the Developer API.
        /// </summary>
        /// <param name="text"></param>
        public void DisplayMessage(string text)
        {
            XmlRpcRequest client = new XmlRpcRequest();
            client.MethodName = "displayMessage";
            client.Params.Add(text);
            client.Send(EnsoUrl);
        }

        /// <summary>
        /// This is getFileSelection in the Developer API.
        /// </summary>
        /// <returns></returns>
        public ArrayList GetFileSelection()
        {
            XmlRpcRequest client = new XmlRpcRequest();
            client.MethodName = "getFileSelection";
            XmlRpcResponse response = client.Send(EnsoUrl);

            if (response.IsFault)
                return null;
            else
                return response.Value as ArrayList;
        }

        /// <summary>
        /// This is getUnicodeSelection in the Developer API.
        /// </summary>
        /// <returns></returns>
        public string GetUnicodeSelection()
        {
            XmlRpcRequest client = new XmlRpcRequest();
            client.MethodName = "getUnicodeSelection";
            XmlRpcResponse response = client.Send(EnsoUrl);

            if (response.IsFault)
                return null;
            else
                return response.Value as string;
        }

        /// <summary>
        /// This is insertUnicodeAtCursor in the Developer API.
        /// </summary>
        /// <param name="text"></param>
        /// <param name="fromCommand"></param>
        public void InsertUnicodeAtCursor(string text, string fromCommand)
        {
            XmlRpcRequest client = new XmlRpcRequest();
            client.MethodName = "insertUnicodeAtCursor";
            client.Params.Add(text);
            client.Params.Add(fromCommand);
            client.Send(EnsoUrl);
        }

        /// <summary>
        /// This is setUnicodeSelection in the Developer API.
        /// </summary>
        /// <param name="text"></param>
        /// <param name="fromCommand"></param>
        public void SetUnicodeSelection(string text, string fromCommand)
        {
            XmlRpcRequest client = new XmlRpcRequest();
            client.MethodName = "setUnicodeSelection";
            client.Params.Add(text);
            client.Params.Add(fromCommand);
            client.Send(EnsoUrl);
        }

        /// <summary>
        /// This is setCommandValidPostfixes.
        /// TODO: Check and make sure it works.
        /// </summary>
        /// <param name="name"></param>
        /// <param name="postfixes"></param>
        public void SetCommandValidPostfixes(string name, string postfixes)
        {
            XmlRpcRequest client = new XmlRpcRequest();
            client.MethodName = "setCommandValidPostfixes";
            client.Params.Add(ExtensionUrl);
            client.Params.Add(name);
            client.Params.Add(postfixes);
            client.Send(EnsoUrl);
        }

        private string EnsoUrl
        {
            get { return string.Format("{0}:{1}", EnsoAddress, EnsoPort); }
        }

        private string ExtensionUrl
        {
            get { return string.Format("http://{0}:{1}", ExtensionAddress, _port); }
        }
    }
}